001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2009, by Object Refinery Limited and Contributors.
006 *
007 * Project Info: http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022 * USA.
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * ---------------------------------
028 * AbstractCategoryItemRenderer.java
029 * ---------------------------------
030 * (C) Copyright 2002-2009, by Object Refinery Limited.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): Richard Atkinson;
034 * Peter Kolb (patch 2497611);
035 *
036 * Changes:
037 * --------
038 * 29-May-2002 : Version 1 (DG);
039 * 06-Jun-2002 : Added accessor methods for the tool tip generator (DG);
040 * 11-Jun-2002 : Made constructors protected (DG);
041 * 26-Jun-2002 : Added axis to initialise method (DG);
042 * 05-Aug-2002 : Added urlGenerator member variable plus accessors (RA);
043 * 22-Aug-2002 : Added categoriesPaint attribute, based on code submitted by
044 * Janet Banks. This can be used when there is only one series,
045 * and you want each category item to have a different color (DG);
046 * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
047 * 29-Oct-2002 : Fixed bug where background image for plot was not being
048 * drawn (DG);
049 * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG);
050 * 26-Nov 2002 : Replaced the isStacked() method with getRangeType() (DG);
051 * 09-Jan-2003 : Renamed grid-line methods (DG);
052 * 17-Jan-2003 : Moved plot classes into separate package (DG);
053 * 25-Mar-2003 : Implemented Serializable (DG);
054 * 12-May-2003 : Modified to take into account the plot orientation (DG);
055 * 12-Aug-2003 : Very minor javadoc corrections (DB)
056 * 13-Aug-2003 : Implemented Cloneable (DG);
057 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
058 * 05-Nov-2003 : Fixed marker rendering bug (833623) (DG);
059 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
060 * 11-Feb-2004 : Modified labelling for markers (DG);
061 * 12-Feb-2004 : Updated clone() method (DG);
062 * 15-Apr-2004 : Created a new CategoryToolTipGenerator interface (DG);
063 * 05-May-2004 : Fixed bug (948310) where interval markers extend outside axis
064 * range (DG);
065 * 14-Jun-2004 : Fixed bug in drawRangeMarker() method - now uses 'paint' and
066 * 'stroke' rather than 'outlinePaint' and 'outlineStroke' (DG);
067 * 15-Jun-2004 : Interval markers can now use GradientPaint (DG);
068 * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities
069 * --> TextUtilities (DG);
070 * 01-Oct-2004 : Fixed bug 1029697, problem with label alignment in
071 * drawRangeMarker() method (DG);
072 * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds() (DG);
073 * 21-Jan-2005 : Modified return type of calculateRangeMarkerTextAnchorPoint()
074 * method (DG);
075 * 08-Mar-2005 : Fixed positioning of marker labels (DG);
076 * 20-Apr-2005 : Added legend label, tooltip and URL generators (DG);
077 * 01-Jun-2005 : Handle one dimension of the marker label adjustment
078 * automatically (DG);
079 * 09-Jun-2005 : Added utility method for adding an item entity (DG);
080 * ------------- JFREECHART 1.0.x ---------------------------------------------
081 * 01-Mar-2006 : Updated getLegendItems() to check seriesVisibleInLegend
082 * flags (DG);
083 * 20-Jul-2006 : Set dataset and series indices in LegendItem (DG);
084 * 23-Oct-2006 : Draw outlines for interval markers (DG);
085 * 24-Oct-2006 : Respect alpha setting in markers, as suggested by Sergei
086 * Ivanov in patch 1567843 (DG);
087 * 30-Nov-2006 : Added a check for series visibility in the getLegendItem()
088 * method (DG);
089 * 07-Dec-2006 : Fix for equals() method (DG);
090 * 22-Feb-2007 : Added createState() method (DG);
091 * 01-Mar-2007 : Fixed interval marker drawing (patch 1670686 thanks to
092 * Sergei Ivanov) (DG);
093 * 20-Apr-2007 : Updated getLegendItem() for renderer change, and deprecated
094 * itemLabelGenerator, toolTipGenerator and itemURLGenerator
095 * override fields (DG);
096 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
097 * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG);
098 * 26-Jun-2008 : Added crosshair support (DG);
099 * 25-Nov-2008 : Fixed bug in findRangeBounds() method (DG);
100 * 14-Jan-2009 : Update initialise() to store visible series indices (PK);
101 * 21-Jan-2009 : Added drawRangeLine() method (DG);
102 * 27-Mar-2009 : Added new findRangeBounds() method to account for hidden
103 * series (DG);
104 * 01-Apr-2009 : Added new addEntity() method (DG);
105 *
106 */
107
108 package org.jfree.chart.renderer.category;
109
110 import java.awt.AlphaComposite;
111 import java.awt.Composite;
112 import java.awt.Font;
113 import java.awt.GradientPaint;
114 import java.awt.Graphics2D;
115 import java.awt.Paint;
116 import java.awt.Shape;
117 import java.awt.Stroke;
118 import java.awt.geom.Ellipse2D;
119 import java.awt.geom.Line2D;
120 import java.awt.geom.Point2D;
121 import java.awt.geom.Rectangle2D;
122 import java.io.Serializable;
123
124 import java.util.ArrayList;
125 import java.util.List;
126 import org.jfree.chart.LegendItem;
127 import org.jfree.chart.LegendItemCollection;
128 import org.jfree.chart.axis.CategoryAxis;
129 import org.jfree.chart.axis.ValueAxis;
130 import org.jfree.chart.entity.CategoryItemEntity;
131 import org.jfree.chart.entity.EntityCollection;
132 import org.jfree.chart.event.RendererChangeEvent;
133 import org.jfree.chart.labels.CategoryItemLabelGenerator;
134 import org.jfree.chart.labels.CategorySeriesLabelGenerator;
135 import org.jfree.chart.labels.CategoryToolTipGenerator;
136 import org.jfree.chart.labels.ItemLabelPosition;
137 import org.jfree.chart.labels.StandardCategorySeriesLabelGenerator;
138 import org.jfree.chart.plot.CategoryCrosshairState;
139 import org.jfree.chart.plot.CategoryMarker;
140 import org.jfree.chart.plot.CategoryPlot;
141 import org.jfree.chart.plot.DrawingSupplier;
142 import org.jfree.chart.plot.IntervalMarker;
143 import org.jfree.chart.plot.Marker;
144 import org.jfree.chart.plot.PlotOrientation;
145 import org.jfree.chart.plot.PlotRenderingInfo;
146 import org.jfree.chart.plot.ValueMarker;
147 import org.jfree.chart.renderer.AbstractRenderer;
148 import org.jfree.chart.urls.CategoryURLGenerator;
149 import org.jfree.data.Range;
150 import org.jfree.data.category.CategoryDataset;
151 import org.jfree.data.general.DatasetUtilities;
152 import org.jfree.text.TextUtilities;
153 import org.jfree.ui.GradientPaintTransformer;
154 import org.jfree.ui.LengthAdjustmentType;
155 import org.jfree.ui.RectangleAnchor;
156 import org.jfree.ui.RectangleEdge;
157 import org.jfree.ui.RectangleInsets;
158 import org.jfree.util.ObjectList;
159 import org.jfree.util.ObjectUtilities;
160 import org.jfree.util.PublicCloneable;
161
162 /**
163 * An abstract base class that you can use to implement a new
164 * {@link CategoryItemRenderer}. When you create a new
165 * {@link CategoryItemRenderer} you are not required to extend this class,
166 * but it makes the job easier.
167 */
168 public abstract class AbstractCategoryItemRenderer extends AbstractRenderer
169 implements CategoryItemRenderer, Cloneable, PublicCloneable,
170 Serializable {
171
172 /** For serialization. */
173 private static final long serialVersionUID = 1247553218442497391L;
174
175 /** The plot that the renderer is assigned to. */
176 private CategoryPlot plot;
177
178 /** A list of item label generators (one per series). */
179 private ObjectList itemLabelGeneratorList;
180
181 /** The base item label generator. */
182 private CategoryItemLabelGenerator baseItemLabelGenerator;
183
184 /** A list of tool tip generators (one per series). */
185 private ObjectList toolTipGeneratorList;
186
187 /** The base tool tip generator. */
188 private CategoryToolTipGenerator baseToolTipGenerator;
189
190 /** A list of item label generators (one per series). */
191 private ObjectList itemURLGeneratorList;
192
193 /** The base item label generator. */
194 private CategoryURLGenerator baseItemURLGenerator;
195
196 /** The legend item label generator. */
197 private CategorySeriesLabelGenerator legendItemLabelGenerator;
198
199 /** The legend item tool tip generator. */
200 private CategorySeriesLabelGenerator legendItemToolTipGenerator;
201
202 /** The legend item URL generator. */
203 private CategorySeriesLabelGenerator legendItemURLGenerator;
204
205 /** The number of rows in the dataset (temporary record). */
206 private transient int rowCount;
207
208 /** The number of columns in the dataset (temporary record). */
209 private transient int columnCount;
210
211 /**
212 * Creates a new renderer with no tool tip generator and no URL generator.
213 * The defaults (no tool tip or URL generators) have been chosen to
214 * minimise the processing required to generate a default chart. If you
215 * require tool tips or URLs, then you can easily add the required
216 * generators.
217 */
218 protected AbstractCategoryItemRenderer() {
219 this.itemLabelGenerator = null;
220 this.itemLabelGeneratorList = new ObjectList();
221 this.toolTipGenerator = null;
222 this.toolTipGeneratorList = new ObjectList();
223 this.itemURLGenerator = null;
224 this.itemURLGeneratorList = new ObjectList();
225 this.legendItemLabelGenerator
226 = new StandardCategorySeriesLabelGenerator();
227 }
228
229 /**
230 * Returns the number of passes through the dataset required by the
231 * renderer. This method returns <code>1</code>, subclasses should
232 * override if they need more passes.
233 *
234 * @return The pass count.
235 */
236 public int getPassCount() {
237 return 1;
238 }
239
240 /**
241 * Returns the plot that the renderer has been assigned to (where
242 * <code>null</code> indicates that the renderer is not currently assigned
243 * to a plot).
244 *
245 * @return The plot (possibly <code>null</code>).
246 *
247 * @see #setPlot(CategoryPlot)
248 */
249 public CategoryPlot getPlot() {
250 return this.plot;
251 }
252
253 /**
254 * Sets the plot that the renderer has been assigned to. This method is
255 * usually called by the {@link CategoryPlot}, in normal usage you
256 * shouldn't need to call this method directly.
257 *
258 * @param plot the plot (<code>null</code> not permitted).
259 *
260 * @see #getPlot()
261 */
262 public void setPlot(CategoryPlot plot) {
263 if (plot == null) {
264 throw new IllegalArgumentException("Null 'plot' argument.");
265 }
266 this.plot = plot;
267 }
268
269 // ITEM LABEL GENERATOR
270
271 /**
272 * Returns the item label generator for a data item. This implementation
273 * simply passes control to the {@link #getSeriesItemLabelGenerator(int)}
274 * method. If, for some reason, you want a different generator for
275 * individual items, you can override this method.
276 *
277 * @param row the row index (zero based).
278 * @param column the column index (zero based).
279 *
280 * @return The generator (possibly <code>null</code>).
281 */
282 public CategoryItemLabelGenerator getItemLabelGenerator(int row,
283 int column) {
284 return getSeriesItemLabelGenerator(row);
285 }
286
287 /**
288 * Returns the item label generator for a series.
289 *
290 * @param series the series index (zero based).
291 *
292 * @return The generator (possibly <code>null</code>).
293 *
294 * @see #setSeriesItemLabelGenerator(int, CategoryItemLabelGenerator)
295 */
296 public CategoryItemLabelGenerator getSeriesItemLabelGenerator(int series) {
297
298 // return the generator for ALL series, if there is one...
299 if (this.itemLabelGenerator != null) {
300 return this.itemLabelGenerator;
301 }
302
303 // otherwise look up the generator table
304 CategoryItemLabelGenerator generator = (CategoryItemLabelGenerator)
305 this.itemLabelGeneratorList.get(series);
306 if (generator == null) {
307 generator = this.baseItemLabelGenerator;
308 }
309 return generator;
310
311 }
312
313 /**
314 * Sets the item label generator for a series and sends a
315 * {@link RendererChangeEvent} to all registered listeners.
316 *
317 * @param series the series index (zero based).
318 * @param generator the generator (<code>null</code> permitted).
319 *
320 * @see #getSeriesItemLabelGenerator(int)
321 */
322 public void setSeriesItemLabelGenerator(int series,
323 CategoryItemLabelGenerator generator) {
324 this.itemLabelGeneratorList.set(series, generator);
325 fireChangeEvent();
326 }
327
328 /**
329 * Returns the base item label generator.
330 *
331 * @return The generator (possibly <code>null</code>).
332 *
333 * @see #setBaseItemLabelGenerator(CategoryItemLabelGenerator)
334 */
335 public CategoryItemLabelGenerator getBaseItemLabelGenerator() {
336 return this.baseItemLabelGenerator;
337 }
338
339 /**
340 * Sets the base item label generator and sends a
341 * {@link RendererChangeEvent} to all registered listeners.
342 *
343 * @param generator the generator (<code>null</code> permitted).
344 *
345 * @see #getBaseItemLabelGenerator()
346 */
347 public void setBaseItemLabelGenerator(
348 CategoryItemLabelGenerator generator) {
349 this.baseItemLabelGenerator = generator;
350 fireChangeEvent();
351 }
352
353 // TOOL TIP GENERATOR
354
355 /**
356 * Returns the tool tip generator that should be used for the specified
357 * item. This method looks up the generator using the "three-layer"
358 * approach outlined in the general description of this interface. You
359 * can override this method if you want to return a different generator per
360 * item.
361 *
362 * @param row the row index (zero-based).
363 * @param column the column index (zero-based).
364 *
365 * @return The generator (possibly <code>null</code>).
366 */
367 public CategoryToolTipGenerator getToolTipGenerator(int row, int column) {
368
369 CategoryToolTipGenerator result = null;
370 if (this.toolTipGenerator != null) {
371 result = this.toolTipGenerator;
372 }
373 else {
374 result = getSeriesToolTipGenerator(row);
375 if (result == null) {
376 result = this.baseToolTipGenerator;
377 }
378 }
379 return result;
380 }
381
382 /**
383 * Returns the tool tip generator for the specified series (a "layer 1"
384 * generator).
385 *
386 * @param series the series index (zero-based).
387 *
388 * @return The tool tip generator (possibly <code>null</code>).
389 *
390 * @see #setSeriesToolTipGenerator(int, CategoryToolTipGenerator)
391 */
392 public CategoryToolTipGenerator getSeriesToolTipGenerator(int series) {
393 return (CategoryToolTipGenerator) this.toolTipGeneratorList.get(series);
394 }
395
396 /**
397 * Sets the tool tip generator for a series and sends a
398 * {@link RendererChangeEvent} to all registered listeners.
399 *
400 * @param series the series index (zero-based).
401 * @param generator the generator (<code>null</code> permitted).
402 *
403 * @see #getSeriesToolTipGenerator(int)
404 */
405 public void setSeriesToolTipGenerator(int series,
406 CategoryToolTipGenerator generator) {
407 this.toolTipGeneratorList.set(series, generator);
408 fireChangeEvent();
409 }
410
411 /**
412 * Returns the base tool tip generator (the "layer 2" generator).
413 *
414 * @return The tool tip generator (possibly <code>null</code>).
415 *
416 * @see #setBaseToolTipGenerator(CategoryToolTipGenerator)
417 */
418 public CategoryToolTipGenerator getBaseToolTipGenerator() {
419 return this.baseToolTipGenerator;
420 }
421
422 /**
423 * Sets the base tool tip generator and sends a {@link RendererChangeEvent}
424 * to all registered listeners.
425 *
426 * @param generator the generator (<code>null</code> permitted).
427 *
428 * @see #getBaseToolTipGenerator()
429 */
430 public void setBaseToolTipGenerator(CategoryToolTipGenerator generator) {
431 this.baseToolTipGenerator = generator;
432 fireChangeEvent();
433 }
434
435 // URL GENERATOR
436
437 /**
438 * Returns the URL generator for a data item. This method just calls the
439 * getSeriesItemURLGenerator method, but you can override this behaviour if
440 * you want to.
441 *
442 * @param row the row index (zero based).
443 * @param column the column index (zero based).
444 *
445 * @return The URL generator.
446 */
447 public CategoryURLGenerator getItemURLGenerator(int row, int column) {
448 return getSeriesItemURLGenerator(row);
449 }
450
451 /**
452 * Returns the URL generator for a series.
453 *
454 * @param series the series index (zero based).
455 *
456 * @return The URL generator for the series.
457 *
458 * @see #setSeriesItemURLGenerator(int, CategoryURLGenerator)
459 */
460 public CategoryURLGenerator getSeriesItemURLGenerator(int series) {
461
462 // return the generator for ALL series, if there is one...
463 if (this.itemURLGenerator != null) {
464 return this.itemURLGenerator;
465 }
466
467 // otherwise look up the generator table
468 CategoryURLGenerator generator
469 = (CategoryURLGenerator) this.itemURLGeneratorList.get(series);
470 if (generator == null) {
471 generator = this.baseItemURLGenerator;
472 }
473 return generator;
474
475 }
476
477 /**
478 * Sets the URL generator for a series and sends a
479 * {@link RendererChangeEvent} to all registered listeners.
480 *
481 * @param series the series index (zero based).
482 * @param generator the generator.
483 *
484 * @see #getSeriesItemURLGenerator(int)
485 */
486 public void setSeriesItemURLGenerator(int series,
487 CategoryURLGenerator generator) {
488 this.itemURLGeneratorList.set(series, generator);
489 fireChangeEvent();
490 }
491
492 /**
493 * Returns the base item URL generator.
494 *
495 * @return The item URL generator.
496 *
497 * @see #setBaseItemURLGenerator(CategoryURLGenerator)
498 */
499 public CategoryURLGenerator getBaseItemURLGenerator() {
500 return this.baseItemURLGenerator;
501 }
502
503 /**
504 * Sets the base item URL generator and sends a
505 * {@link RendererChangeEvent} to all registered listeners.
506 *
507 * @param generator the item URL generator (<code>null</code> permitted).
508 *
509 * @see #getBaseItemURLGenerator()
510 */
511 public void setBaseItemURLGenerator(CategoryURLGenerator generator) {
512 this.baseItemURLGenerator = generator;
513 fireChangeEvent();
514 }
515
516 /**
517 * Returns the number of rows in the dataset. This value is updated in the
518 * {@link AbstractCategoryItemRenderer#initialise} method.
519 *
520 * @return The row count.
521 */
522 public int getRowCount() {
523 return this.rowCount;
524 }
525
526 /**
527 * Returns the number of columns in the dataset. This value is updated in
528 * the {@link AbstractCategoryItemRenderer#initialise} method.
529 *
530 * @return The column count.
531 */
532 public int getColumnCount() {
533 return this.columnCount;
534 }
535
536 /**
537 * Creates a new state instance---this method is called from the
538 * {@link #initialise(Graphics2D, Rectangle2D, CategoryPlot, int,
539 * PlotRenderingInfo)} method. Subclasses can override this method if
540 * they need to use a subclass of {@link CategoryItemRendererState}.
541 *
542 * @param info collects plot rendering info (<code>null</code> permitted).
543 *
544 * @return The new state instance (never <code>null</code>).
545 *
546 * @since 1.0.5
547 */
548 protected CategoryItemRendererState createState(PlotRenderingInfo info) {
549 return new CategoryItemRendererState(info);
550 }
551
552 /**
553 * Initialises the renderer and returns a state object that will be used
554 * for the remainder of the drawing process for a single chart. The state
555 * object allows for the fact that the renderer may be used simultaneously
556 * by multiple threads (each thread will work with a separate state object).
557 *
558 * @param g2 the graphics device.
559 * @param dataArea the data area.
560 * @param plot the plot.
561 * @param rendererIndex the renderer index.
562 * @param info an object for returning information about the structure of
563 * the plot (<code>null</code> permitted).
564 *
565 * @return The renderer state.
566 */
567 public CategoryItemRendererState initialise(Graphics2D g2,
568 Rectangle2D dataArea,
569 CategoryPlot plot,
570 int rendererIndex,
571 PlotRenderingInfo info) {
572
573 setPlot(plot);
574 CategoryDataset data = plot.getDataset(rendererIndex);
575 if (data != null) {
576 this.rowCount = data.getRowCount();
577 this.columnCount = data.getColumnCount();
578 }
579 else {
580 this.rowCount = 0;
581 this.columnCount = 0;
582 }
583 CategoryItemRendererState state = createState(info);
584 int[] visibleSeriesTemp = new int[this.rowCount];
585 int visibleSeriesCount = 0;
586 for (int row = 0; row < this.rowCount; row++){
587 if (isSeriesVisible(row)) {
588 visibleSeriesTemp[visibleSeriesCount] = row;
589 visibleSeriesCount++;
590 }
591 }
592 int[] visibleSeries = new int[visibleSeriesCount];
593 System.arraycopy(visibleSeriesTemp, 0, visibleSeries, 0,
594 visibleSeriesCount);
595 state.setVisibleSeriesArray(visibleSeries);
596 return state;
597 }
598
599 /**
600 * Returns the range of values the renderer requires to display all the
601 * items from the specified dataset.
602 *
603 * @param dataset the dataset (<code>null</code> permitted).
604 *
605 * @return The range (or <code>null</code> if the dataset is
606 * <code>null</code> or empty).
607 */
608 public Range findRangeBounds(CategoryDataset dataset) {
609 return findRangeBounds(dataset, false);
610 }
611
612 /**
613 * Returns the range of values the renderer requires to display all the
614 * items from the specified dataset.
615 *
616 * @param dataset the dataset (<code>null</code> permitted).
617 * @param includeInterval include the y-interval if the dataset has one.
618 *
619 * @return The range (<code>null</code> if the dataset is <code>null</code>
620 * or empty).
621 *
622 * @since 1.0.13
623 */
624 protected Range findRangeBounds(CategoryDataset dataset,
625 boolean includeInterval) {
626 if (dataset == null) {
627 return null;
628 }
629 if (getDataBoundsIncludesVisibleSeriesOnly()) {
630 List visibleSeriesKeys = new ArrayList();
631 int seriesCount = dataset.getRowCount();
632 for (int s = 0; s < seriesCount; s++) {
633 if (isSeriesVisible(s)) {
634 visibleSeriesKeys.add(dataset.getRowKey(s));
635 }
636 }
637 return DatasetUtilities.findRangeBounds(dataset,
638 visibleSeriesKeys, includeInterval);
639 }
640 else {
641 return DatasetUtilities.findRangeBounds(dataset, includeInterval);
642 }
643 }
644
645 /**
646 * Returns the Java2D coordinate for the middle of the specified data item.
647 *
648 * @param rowKey the row key.
649 * @param columnKey the column key.
650 * @param dataset the dataset.
651 * @param axis the axis.
652 * @param area the data area.
653 * @param edge the edge along which the axis lies.
654 *
655 * @return The Java2D coordinate for the middle of the item.
656 *
657 * @since 1.0.11
658 */
659 public double getItemMiddle(Comparable rowKey, Comparable columnKey,
660 CategoryDataset dataset, CategoryAxis axis, Rectangle2D area,
661 RectangleEdge edge) {
662 return axis.getCategoryMiddle(columnKey, dataset.getColumnKeys(), area,
663 edge);
664 }
665
666 /**
667 * Draws a background for the data area. The default implementation just
668 * gets the plot to draw the background, but some renderers will override
669 * this behaviour.
670 *
671 * @param g2 the graphics device.
672 * @param plot the plot.
673 * @param dataArea the data area.
674 */
675 public void drawBackground(Graphics2D g2,
676 CategoryPlot plot,
677 Rectangle2D dataArea) {
678
679 plot.drawBackground(g2, dataArea);
680
681 }
682
683 /**
684 * Draws an outline for the data area. The default implementation just
685 * gets the plot to draw the outline, but some renderers will override this
686 * behaviour.
687 *
688 * @param g2 the graphics device.
689 * @param plot the plot.
690 * @param dataArea the data area.
691 */
692 public void drawOutline(Graphics2D g2,
693 CategoryPlot plot,
694 Rectangle2D dataArea) {
695
696 plot.drawOutline(g2, dataArea);
697
698 }
699
700 /**
701 * Draws a grid line against the domain axis.
702 * <P>
703 * Note that this default implementation assumes that the horizontal axis
704 * is the domain axis. If this is not the case, you will need to override
705 * this method.
706 *
707 * @param g2 the graphics device.
708 * @param plot the plot.
709 * @param dataArea the area for plotting data (not yet adjusted for any
710 * 3D effect).
711 * @param value the Java2D value at which the grid line should be drawn.
712 *
713 * @see #drawRangeGridline(Graphics2D, CategoryPlot, ValueAxis,
714 * Rectangle2D, double)
715 */
716 public void drawDomainGridline(Graphics2D g2,
717 CategoryPlot plot,
718 Rectangle2D dataArea,
719 double value) {
720
721 Line2D line = null;
722 PlotOrientation orientation = plot.getOrientation();
723
724 if (orientation == PlotOrientation.HORIZONTAL) {
725 line = new Line2D.Double(dataArea.getMinX(), value,
726 dataArea.getMaxX(), value);
727 }
728 else if (orientation == PlotOrientation.VERTICAL) {
729 line = new Line2D.Double(value, dataArea.getMinY(), value,
730 dataArea.getMaxY());
731 }
732
733 Paint paint = plot.getDomainGridlinePaint();
734 if (paint == null) {
735 paint = CategoryPlot.DEFAULT_GRIDLINE_PAINT;
736 }
737 g2.setPaint(paint);
738
739 Stroke stroke = plot.getDomainGridlineStroke();
740 if (stroke == null) {
741 stroke = CategoryPlot.DEFAULT_GRIDLINE_STROKE;
742 }
743 g2.setStroke(stroke);
744
745 g2.draw(line);
746
747 }
748
749 /**
750 * Draws a grid line against the range axis.
751 *
752 * @param g2 the graphics device.
753 * @param plot the plot.
754 * @param axis the value axis.
755 * @param dataArea the area for plotting data (not yet adjusted for any
756 * 3D effect).
757 * @param value the value at which the grid line should be drawn.
758 *
759 * @see #drawDomainGridline(Graphics2D, CategoryPlot, Rectangle2D, double)
760 */
761 public void drawRangeGridline(Graphics2D g2,
762 CategoryPlot plot,
763 ValueAxis axis,
764 Rectangle2D dataArea,
765 double value) {
766
767 Range range = axis.getRange();
768 if (!range.contains(value)) {
769 return;
770 }
771
772 PlotOrientation orientation = plot.getOrientation();
773 double v = axis.valueToJava2D(value, dataArea, plot.getRangeAxisEdge());
774 Line2D line = null;
775 if (orientation == PlotOrientation.HORIZONTAL) {
776 line = new Line2D.Double(v, dataArea.getMinY(), v,
777 dataArea.getMaxY());
778 }
779 else if (orientation == PlotOrientation.VERTICAL) {
780 line = new Line2D.Double(dataArea.getMinX(), v,
781 dataArea.getMaxX(), v);
782 }
783
784 Paint paint = plot.getRangeGridlinePaint();
785 if (paint == null) {
786 paint = CategoryPlot.DEFAULT_GRIDLINE_PAINT;
787 }
788 g2.setPaint(paint);
789
790 Stroke stroke = plot.getRangeGridlineStroke();
791 if (stroke == null) {
792 stroke = CategoryPlot.DEFAULT_GRIDLINE_STROKE;
793 }
794 g2.setStroke(stroke);
795
796 g2.draw(line);
797
798 }
799
800 /**
801 * Draws a line perpendicular to the range axis.
802 *
803 * @param g2 the graphics device.
804 * @param plot the plot.
805 * @param axis the value axis.
806 * @param dataArea the area for plotting data (not yet adjusted for any 3D
807 * effect).
808 * @param value the value at which the grid line should be drawn.
809 * @param paint the paint (<code>null</code> not permitted).
810 * @param stroke the stroke (<code>null</code> not permitted).
811 *
812 * @see #drawRangeGridline
813 *
814 * @since 1.0.13
815 */
816 public void drawRangeLine(Graphics2D g2, CategoryPlot plot, ValueAxis axis,
817 Rectangle2D dataArea, double value, Paint paint, Stroke stroke) {
818
819 // TODO: In JFreeChart 1.2.0, put this method in the
820 // CategoryItemRenderer interface
821 Range range = axis.getRange();
822 if (!range.contains(value)) {
823 return;
824 }
825
826 PlotOrientation orientation = plot.getOrientation();
827 Line2D line = null;
828 double v = axis.valueToJava2D(value, dataArea, plot.getRangeAxisEdge());
829 if (orientation == PlotOrientation.HORIZONTAL) {
830 line = new Line2D.Double(v, dataArea.getMinY(), v,
831 dataArea.getMaxY());
832 }
833 else if (orientation == PlotOrientation.VERTICAL) {
834 line = new Line2D.Double(dataArea.getMinX(), v,
835 dataArea.getMaxX(), v);
836 }
837
838 g2.setPaint(paint);
839 g2.setStroke(stroke);
840 g2.draw(line);
841
842 }
843
844 /**
845 * Draws a marker for the domain axis.
846 *
847 * @param g2 the graphics device (not <code>null</code>).
848 * @param plot the plot (not <code>null</code>).
849 * @param axis the range axis (not <code>null</code>).
850 * @param marker the marker to be drawn (not <code>null</code>).
851 * @param dataArea the area inside the axes (not <code>null</code>).
852 *
853 * @see #drawRangeMarker(Graphics2D, CategoryPlot, ValueAxis, Marker,
854 * Rectangle2D)
855 */
856 public void drawDomainMarker(Graphics2D g2,
857 CategoryPlot plot,
858 CategoryAxis axis,
859 CategoryMarker marker,
860 Rectangle2D dataArea) {
861
862 Comparable category = marker.getKey();
863 CategoryDataset dataset = plot.getDataset(plot.getIndexOf(this));
864 int columnIndex = dataset.getColumnIndex(category);
865 if (columnIndex < 0) {
866 return;
867 }
868
869 final Composite savedComposite = g2.getComposite();
870 g2.setComposite(AlphaComposite.getInstance(
871 AlphaComposite.SRC_OVER, marker.getAlpha()));
872
873 PlotOrientation orientation = plot.getOrientation();
874 Rectangle2D bounds = null;
875 if (marker.getDrawAsLine()) {
876 double v = axis.getCategoryMiddle(columnIndex,
877 dataset.getColumnCount(), dataArea,
878 plot.getDomainAxisEdge());
879 Line2D line = null;
880 if (orientation == PlotOrientation.HORIZONTAL) {
881 line = new Line2D.Double(dataArea.getMinX(), v,
882 dataArea.getMaxX(), v);
883 }
884 else if (orientation == PlotOrientation.VERTICAL) {
885 line = new Line2D.Double(v, dataArea.getMinY(), v,
886 dataArea.getMaxY());
887 }
888 g2.setPaint(marker.getPaint());
889 g2.setStroke(marker.getStroke());
890 g2.draw(line);
891 bounds = line.getBounds2D();
892 }
893 else {
894 double v0 = axis.getCategoryStart(columnIndex,
895 dataset.getColumnCount(), dataArea,
896 plot.getDomainAxisEdge());
897 double v1 = axis.getCategoryEnd(columnIndex,
898 dataset.getColumnCount(), dataArea,
899 plot.getDomainAxisEdge());
900 Rectangle2D area = null;
901 if (orientation == PlotOrientation.HORIZONTAL) {
902 area = new Rectangle2D.Double(dataArea.getMinX(), v0,
903 dataArea.getWidth(), (v1 - v0));
904 }
905 else if (orientation == PlotOrientation.VERTICAL) {
906 area = new Rectangle2D.Double(v0, dataArea.getMinY(),
907 (v1 - v0), dataArea.getHeight());
908 }
909 g2.setPaint(marker.getPaint());
910 g2.fill(area);
911 bounds = area;
912 }
913
914 String label = marker.getLabel();
915 RectangleAnchor anchor = marker.getLabelAnchor();
916 if (label != null) {
917 Font labelFont = marker.getLabelFont();
918 g2.setFont(labelFont);
919 g2.setPaint(marker.getLabelPaint());
920 Point2D coordinates = calculateDomainMarkerTextAnchorPoint(
921 g2, orientation, dataArea, bounds, marker.getLabelOffset(),
922 marker.getLabelOffsetType(), anchor);
923 TextUtilities.drawAlignedString(label, g2,
924 (float) coordinates.getX(), (float) coordinates.getY(),
925 marker.getLabelTextAnchor());
926 }
927 g2.setComposite(savedComposite);
928 }
929
930 /**
931 * Draws a marker for the range axis.
932 *
933 * @param g2 the graphics device (not <code>null</code>).
934 * @param plot the plot (not <code>null</code>).
935 * @param axis the range axis (not <code>null</code>).
936 * @param marker the marker to be drawn (not <code>null</code>).
937 * @param dataArea the area inside the axes (not <code>null</code>).
938 *
939 * @see #drawDomainMarker(Graphics2D, CategoryPlot, CategoryAxis,
940 * CategoryMarker, Rectangle2D)
941 */
942 public void drawRangeMarker(Graphics2D g2,
943 CategoryPlot plot,
944 ValueAxis axis,
945 Marker marker,
946 Rectangle2D dataArea) {
947
948 if (marker instanceof ValueMarker) {
949 ValueMarker vm = (ValueMarker) marker;
950 double value = vm.getValue();
951 Range range = axis.getRange();
952
953 if (!range.contains(value)) {
954 return;
955 }
956
957 final Composite savedComposite = g2.getComposite();
958 g2.setComposite(AlphaComposite.getInstance(
959 AlphaComposite.SRC_OVER, marker.getAlpha()));
960
961 PlotOrientation orientation = plot.getOrientation();
962 double v = axis.valueToJava2D(value, dataArea,
963 plot.getRangeAxisEdge());
964 Line2D line = null;
965 if (orientation == PlotOrientation.HORIZONTAL) {
966 line = new Line2D.Double(v, dataArea.getMinY(), v,
967 dataArea.getMaxY());
968 }
969 else if (orientation == PlotOrientation.VERTICAL) {
970 line = new Line2D.Double(dataArea.getMinX(), v,
971 dataArea.getMaxX(), v);
972 }
973
974 g2.setPaint(marker.getPaint());
975 g2.setStroke(marker.getStroke());
976 g2.draw(line);
977
978 String label = marker.getLabel();
979 RectangleAnchor anchor = marker.getLabelAnchor();
980 if (label != null) {
981 Font labelFont = marker.getLabelFont();
982 g2.setFont(labelFont);
983 g2.setPaint(marker.getLabelPaint());
984 Point2D coordinates = calculateRangeMarkerTextAnchorPoint(
985 g2, orientation, dataArea, line.getBounds2D(),
986 marker.getLabelOffset(), LengthAdjustmentType.EXPAND,
987 anchor);
988 TextUtilities.drawAlignedString(label, g2,
989 (float) coordinates.getX(), (float) coordinates.getY(),
990 marker.getLabelTextAnchor());
991 }
992 g2.setComposite(savedComposite);
993 }
994 else if (marker instanceof IntervalMarker) {
995 IntervalMarker im = (IntervalMarker) marker;
996 double start = im.getStartValue();
997 double end = im.getEndValue();
998 Range range = axis.getRange();
999 if (!(range.intersects(start, end))) {
1000 return;
1001 }
1002
1003 final Composite savedComposite = g2.getComposite();
1004 g2.setComposite(AlphaComposite.getInstance(
1005 AlphaComposite.SRC_OVER, marker.getAlpha()));
1006
1007 double start2d = axis.valueToJava2D(start, dataArea,
1008 plot.getRangeAxisEdge());
1009 double end2d = axis.valueToJava2D(end, dataArea,
1010 plot.getRangeAxisEdge());
1011 double low = Math.min(start2d, end2d);
1012 double high = Math.max(start2d, end2d);
1013
1014 PlotOrientation orientation = plot.getOrientation();
1015 Rectangle2D rect = null;
1016 if (orientation == PlotOrientation.HORIZONTAL) {
1017 // clip left and right bounds to data area
1018 low = Math.max(low, dataArea.getMinX());
1019 high = Math.min(high, dataArea.getMaxX());
1020 rect = new Rectangle2D.Double(low,
1021 dataArea.getMinY(), high - low,
1022 dataArea.getHeight());
1023 }
1024 else if (orientation == PlotOrientation.VERTICAL) {
1025 // clip top and bottom bounds to data area
1026 low = Math.max(low, dataArea.getMinY());
1027 high = Math.min(high, dataArea.getMaxY());
1028 rect = new Rectangle2D.Double(dataArea.getMinX(),
1029 low, dataArea.getWidth(),
1030 high - low);
1031 }
1032 Paint p = marker.getPaint();
1033 if (p instanceof GradientPaint) {
1034 GradientPaint gp = (GradientPaint) p;
1035 GradientPaintTransformer t = im.getGradientPaintTransformer();
1036 if (t != null) {
1037 gp = t.transform(gp, rect);
1038 }
1039 g2.setPaint(gp);
1040 }
1041 else {
1042 g2.setPaint(p);
1043 }
1044 g2.fill(rect);
1045
1046 // now draw the outlines, if visible...
1047 if (im.getOutlinePaint() != null && im.getOutlineStroke() != null) {
1048 if (orientation == PlotOrientation.VERTICAL) {
1049 Line2D line = new Line2D.Double();
1050 double x0 = dataArea.getMinX();
1051 double x1 = dataArea.getMaxX();
1052 g2.setPaint(im.getOutlinePaint());
1053 g2.setStroke(im.getOutlineStroke());
1054 if (range.contains(start)) {
1055 line.setLine(x0, start2d, x1, start2d);
1056 g2.draw(line);
1057 }
1058 if (range.contains(end)) {
1059 line.setLine(x0, end2d, x1, end2d);
1060 g2.draw(line);
1061 }
1062 }
1063 else { // PlotOrientation.HORIZONTAL
1064 Line2D line = new Line2D.Double();
1065 double y0 = dataArea.getMinY();
1066 double y1 = dataArea.getMaxY();
1067 g2.setPaint(im.getOutlinePaint());
1068 g2.setStroke(im.getOutlineStroke());
1069 if (range.contains(start)) {
1070 line.setLine(start2d, y0, start2d, y1);
1071 g2.draw(line);
1072 }
1073 if (range.contains(end)) {
1074 line.setLine(end2d, y0, end2d, y1);
1075 g2.draw(line);
1076 }
1077 }
1078 }
1079
1080 String label = marker.getLabel();
1081 RectangleAnchor anchor = marker.getLabelAnchor();
1082 if (label != null) {
1083 Font labelFont = marker.getLabelFont();
1084 g2.setFont(labelFont);
1085 g2.setPaint(marker.getLabelPaint());
1086 Point2D coordinates = calculateRangeMarkerTextAnchorPoint(
1087 g2, orientation, dataArea, rect,
1088 marker.getLabelOffset(), marker.getLabelOffsetType(),
1089 anchor);
1090 TextUtilities.drawAlignedString(label, g2,
1091 (float) coordinates.getX(), (float) coordinates.getY(),
1092 marker.getLabelTextAnchor());
1093 }
1094 g2.setComposite(savedComposite);
1095 }
1096 }
1097
1098 /**
1099 * Calculates the (x, y) coordinates for drawing the label for a marker on
1100 * the range axis.
1101 *
1102 * @param g2 the graphics device.
1103 * @param orientation the plot orientation.
1104 * @param dataArea the data area.
1105 * @param markerArea the rectangle surrounding the marker.
1106 * @param markerOffset the marker offset.
1107 * @param labelOffsetType the label offset type.
1108 * @param anchor the label anchor.
1109 *
1110 * @return The coordinates for drawing the marker label.
1111 */
1112 protected Point2D calculateDomainMarkerTextAnchorPoint(Graphics2D g2,
1113 PlotOrientation orientation,
1114 Rectangle2D dataArea,
1115 Rectangle2D markerArea,
1116 RectangleInsets markerOffset,
1117 LengthAdjustmentType labelOffsetType,
1118 RectangleAnchor anchor) {
1119
1120 Rectangle2D anchorRect = null;
1121 if (orientation == PlotOrientation.HORIZONTAL) {
1122 anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1123 LengthAdjustmentType.CONTRACT, labelOffsetType);
1124 }
1125 else if (orientation == PlotOrientation.VERTICAL) {
1126 anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1127 labelOffsetType, LengthAdjustmentType.CONTRACT);
1128 }
1129 return RectangleAnchor.coordinates(anchorRect, anchor);
1130
1131 }
1132
1133 /**
1134 * Calculates the (x, y) coordinates for drawing a marker label.
1135 *
1136 * @param g2 the graphics device.
1137 * @param orientation the plot orientation.
1138 * @param dataArea the data area.
1139 * @param markerArea the rectangle surrounding the marker.
1140 * @param markerOffset the marker offset.
1141 * @param labelOffsetType the label offset type.
1142 * @param anchor the label anchor.
1143 *
1144 * @return The coordinates for drawing the marker label.
1145 */
1146 protected Point2D calculateRangeMarkerTextAnchorPoint(Graphics2D g2,
1147 PlotOrientation orientation,
1148 Rectangle2D dataArea,
1149 Rectangle2D markerArea,
1150 RectangleInsets markerOffset,
1151 LengthAdjustmentType labelOffsetType,
1152 RectangleAnchor anchor) {
1153
1154 Rectangle2D anchorRect = null;
1155 if (orientation == PlotOrientation.HORIZONTAL) {
1156 anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1157 labelOffsetType, LengthAdjustmentType.CONTRACT);
1158 }
1159 else if (orientation == PlotOrientation.VERTICAL) {
1160 anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1161 LengthAdjustmentType.CONTRACT, labelOffsetType);
1162 }
1163 return RectangleAnchor.coordinates(anchorRect, anchor);
1164
1165 }
1166
1167 /**
1168 * Returns a legend item for a series. This default implementation will
1169 * return <code>null</code> if {@link #isSeriesVisible(int)} or
1170 * {@link #isSeriesVisibleInLegend(int)} returns <code>false</code>.
1171 *
1172 * @param datasetIndex the dataset index (zero-based).
1173 * @param series the series index (zero-based).
1174 *
1175 * @return The legend item (possibly <code>null</code>).
1176 *
1177 * @see #getLegendItems()
1178 */
1179 public LegendItem getLegendItem(int datasetIndex, int series) {
1180
1181 CategoryPlot p = getPlot();
1182 if (p == null) {
1183 return null;
1184 }
1185
1186 // check that a legend item needs to be displayed...
1187 if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
1188 return null;
1189 }
1190
1191 CategoryDataset dataset = p.getDataset(datasetIndex);
1192 String label = this.legendItemLabelGenerator.generateLabel(dataset,
1193 series);
1194 String description = label;
1195 String toolTipText = null;
1196 if (this.legendItemToolTipGenerator != null) {
1197 toolTipText = this.legendItemToolTipGenerator.generateLabel(
1198 dataset, series);
1199 }
1200 String urlText = null;
1201 if (this.legendItemURLGenerator != null) {
1202 urlText = this.legendItemURLGenerator.generateLabel(dataset,
1203 series);
1204 }
1205 Shape shape = lookupLegendShape(series);
1206 Paint paint = lookupSeriesPaint(series);
1207 Paint outlinePaint = lookupSeriesOutlinePaint(series);
1208 Stroke outlineStroke = lookupSeriesOutlineStroke(series);
1209
1210 LegendItem item = new LegendItem(label, description, toolTipText,
1211 urlText, shape, paint, outlineStroke, outlinePaint);
1212 item.setLabelFont(lookupLegendTextFont(series));
1213 Paint labelPaint = lookupLegendTextPaint(series);
1214 if (labelPaint != null) {
1215 item.setLabelPaint(labelPaint);
1216 }
1217 item.setSeriesKey(dataset.getRowKey(series));
1218 item.setSeriesIndex(series);
1219 item.setDataset(dataset);
1220 item.setDatasetIndex(datasetIndex);
1221 return item;
1222 }
1223
1224 /**
1225 * Tests this renderer for equality with another object.
1226 *
1227 * @param obj the object.
1228 *
1229 * @return <code>true</code> or <code>false</code>.
1230 */
1231 public boolean equals(Object obj) {
1232
1233 if (obj == this) {
1234 return true;
1235 }
1236 if (!(obj instanceof AbstractCategoryItemRenderer)) {
1237 return false;
1238 }
1239 AbstractCategoryItemRenderer that = (AbstractCategoryItemRenderer) obj;
1240
1241 if (!ObjectUtilities.equal(this.itemLabelGenerator,
1242 that.itemLabelGenerator)) {
1243 return false;
1244 }
1245 if (!ObjectUtilities.equal(this.itemLabelGeneratorList,
1246 that.itemLabelGeneratorList)) {
1247 return false;
1248 }
1249 if (!ObjectUtilities.equal(this.baseItemLabelGenerator,
1250 that.baseItemLabelGenerator)) {
1251 return false;
1252 }
1253 if (!ObjectUtilities.equal(this.toolTipGenerator,
1254 that.toolTipGenerator)) {
1255 return false;
1256 }
1257 if (!ObjectUtilities.equal(this.toolTipGeneratorList,
1258 that.toolTipGeneratorList)) {
1259 return false;
1260 }
1261 if (!ObjectUtilities.equal(this.baseToolTipGenerator,
1262 that.baseToolTipGenerator)) {
1263 return false;
1264 }
1265 if (!ObjectUtilities.equal(this.itemURLGenerator,
1266 that.itemURLGenerator)) {
1267 return false;
1268 }
1269 if (!ObjectUtilities.equal(this.itemURLGeneratorList,
1270 that.itemURLGeneratorList)) {
1271 return false;
1272 }
1273 if (!ObjectUtilities.equal(this.baseItemURLGenerator,
1274 that.baseItemURLGenerator)) {
1275 return false;
1276 }
1277 if (!ObjectUtilities.equal(this.legendItemLabelGenerator,
1278 that.legendItemLabelGenerator)) {
1279 return false;
1280 }
1281 if (!ObjectUtilities.equal(this.legendItemToolTipGenerator,
1282 that.legendItemToolTipGenerator)) {
1283 return false;
1284 }
1285 if (!ObjectUtilities.equal(this.legendItemURLGenerator,
1286 that.legendItemURLGenerator)) {
1287 return false;
1288 }
1289 return super.equals(obj);
1290 }
1291
1292 /**
1293 * Returns a hash code for the renderer.
1294 *
1295 * @return The hash code.
1296 */
1297 public int hashCode() {
1298 int result = super.hashCode();
1299 return result;
1300 }
1301
1302 /**
1303 * Returns the drawing supplier from the plot.
1304 *
1305 * @return The drawing supplier (possibly <code>null</code>).
1306 */
1307 public DrawingSupplier getDrawingSupplier() {
1308 DrawingSupplier result = null;
1309 CategoryPlot cp = getPlot();
1310 if (cp != null) {
1311 result = cp.getDrawingSupplier();
1312 }
1313 return result;
1314 }
1315
1316 /**
1317 * Considers the current (x, y) coordinate and updates the crosshair point
1318 * if it meets the criteria (usually means the (x, y) coordinate is the
1319 * closest to the anchor point so far).
1320 *
1321 * @param crosshairState the crosshair state (<code>null</code> permitted,
1322 * but the method does nothing in that case).
1323 * @param rowKey the row key.
1324 * @param columnKey the column key.
1325 * @param value the data value.
1326 * @param datasetIndex the dataset index.
1327 * @param transX the x-value translated to Java2D space.
1328 * @param transY the y-value translated to Java2D space.
1329 * @param orientation the plot orientation (<code>null</code> not
1330 * permitted).
1331 *
1332 * @since 1.0.11
1333 */
1334 protected void updateCrosshairValues(CategoryCrosshairState crosshairState,
1335 Comparable rowKey, Comparable columnKey, double value,
1336 int datasetIndex,
1337 double transX, double transY, PlotOrientation orientation) {
1338
1339 if (orientation == null) {
1340 throw new IllegalArgumentException("Null 'orientation' argument.");
1341 }
1342
1343 if (crosshairState != null) {
1344 if (this.plot.isRangeCrosshairLockedOnData()) {
1345 // both axes
1346 crosshairState.updateCrosshairPoint(rowKey, columnKey, value,
1347 datasetIndex, transX, transY, orientation);
1348 }
1349 else {
1350 crosshairState.updateCrosshairX(rowKey, columnKey,
1351 datasetIndex, transX, orientation);
1352 }
1353 }
1354 }
1355
1356 /**
1357 * Draws an item label.
1358 *
1359 * @param g2 the graphics device.
1360 * @param orientation the orientation.
1361 * @param dataset the dataset.
1362 * @param row the row.
1363 * @param column the column.
1364 * @param x the x coordinate (in Java2D space).
1365 * @param y the y coordinate (in Java2D space).
1366 * @param negative indicates a negative value (which affects the item
1367 * label position).
1368 */
1369 protected void drawItemLabel(Graphics2D g2, PlotOrientation orientation,
1370 CategoryDataset dataset, int row, int column,
1371 double x, double y, boolean negative) {
1372
1373 CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
1374 column);
1375 if (generator != null) {
1376 Font labelFont = getItemLabelFont(row, column);
1377 Paint paint = getItemLabelPaint(row, column);
1378 g2.setFont(labelFont);
1379 g2.setPaint(paint);
1380 String label = generator.generateLabel(dataset, row, column);
1381 ItemLabelPosition position = null;
1382 if (!negative) {
1383 position = getPositiveItemLabelPosition(row, column);
1384 }
1385 else {
1386 position = getNegativeItemLabelPosition(row, column);
1387 }
1388 Point2D anchorPoint = calculateLabelAnchorPoint(
1389 position.getItemLabelAnchor(), x, y, orientation);
1390 TextUtilities.drawRotatedString(label, g2,
1391 (float) anchorPoint.getX(), (float) anchorPoint.getY(),
1392 position.getTextAnchor(),
1393 position.getAngle(), position.getRotationAnchor());
1394 }
1395
1396 }
1397
1398 /**
1399 * Returns an independent copy of the renderer. The <code>plot</code>
1400 * reference is shallow copied.
1401 *
1402 * @return A clone.
1403 *
1404 * @throws CloneNotSupportedException can be thrown if one of the objects
1405 * belonging to the renderer does not support cloning (for example,
1406 * an item label generator).
1407 */
1408 public Object clone() throws CloneNotSupportedException {
1409
1410 AbstractCategoryItemRenderer clone
1411 = (AbstractCategoryItemRenderer) super.clone();
1412
1413 if (this.itemLabelGenerator != null) {
1414 if (this.itemLabelGenerator instanceof PublicCloneable) {
1415 PublicCloneable pc = (PublicCloneable) this.itemLabelGenerator;
1416 clone.itemLabelGenerator
1417 = (CategoryItemLabelGenerator) pc.clone();
1418 }
1419 else {
1420 throw new CloneNotSupportedException(
1421 "ItemLabelGenerator not cloneable.");
1422 }
1423 }
1424
1425 if (this.itemLabelGeneratorList != null) {
1426 clone.itemLabelGeneratorList
1427 = (ObjectList) this.itemLabelGeneratorList.clone();
1428 }
1429
1430 if (this.baseItemLabelGenerator != null) {
1431 if (this.baseItemLabelGenerator instanceof PublicCloneable) {
1432 PublicCloneable pc
1433 = (PublicCloneable) this.baseItemLabelGenerator;
1434 clone.baseItemLabelGenerator
1435 = (CategoryItemLabelGenerator) pc.clone();
1436 }
1437 else {
1438 throw new CloneNotSupportedException(
1439 "ItemLabelGenerator not cloneable.");
1440 }
1441 }
1442
1443 if (this.toolTipGenerator != null) {
1444 if (this.toolTipGenerator instanceof PublicCloneable) {
1445 PublicCloneable pc = (PublicCloneable) this.toolTipGenerator;
1446 clone.toolTipGenerator = (CategoryToolTipGenerator) pc.clone();
1447 }
1448 else {
1449 throw new CloneNotSupportedException(
1450 "Tool tip generator not cloneable.");
1451 }
1452 }
1453
1454 if (this.toolTipGeneratorList != null) {
1455 clone.toolTipGeneratorList
1456 = (ObjectList) this.toolTipGeneratorList.clone();
1457 }
1458
1459 if (this.baseToolTipGenerator != null) {
1460 if (this.baseToolTipGenerator instanceof PublicCloneable) {
1461 PublicCloneable pc
1462 = (PublicCloneable) this.baseToolTipGenerator;
1463 clone.baseToolTipGenerator
1464 = (CategoryToolTipGenerator) pc.clone();
1465 }
1466 else {
1467 throw new CloneNotSupportedException(
1468 "Base tool tip generator not cloneable.");
1469 }
1470 }
1471
1472 if (this.itemURLGenerator != null) {
1473 if (this.itemURLGenerator instanceof PublicCloneable) {
1474 PublicCloneable pc = (PublicCloneable) this.itemURLGenerator;
1475 clone.itemURLGenerator = (CategoryURLGenerator) pc.clone();
1476 }
1477 else {
1478 throw new CloneNotSupportedException(
1479 "Item URL generator not cloneable.");
1480 }
1481 }
1482
1483 if (this.itemURLGeneratorList != null) {
1484 clone.itemURLGeneratorList
1485 = (ObjectList) this.itemURLGeneratorList.clone();
1486 }
1487
1488 if (this.baseItemURLGenerator != null) {
1489 if (this.baseItemURLGenerator instanceof PublicCloneable) {
1490 PublicCloneable pc
1491 = (PublicCloneable) this.baseItemURLGenerator;
1492 clone.baseItemURLGenerator = (CategoryURLGenerator) pc.clone();
1493 }
1494 else {
1495 throw new CloneNotSupportedException(
1496 "Base item URL generator not cloneable.");
1497 }
1498 }
1499
1500 if (this.legendItemLabelGenerator instanceof PublicCloneable) {
1501 clone.legendItemLabelGenerator = (CategorySeriesLabelGenerator)
1502 ObjectUtilities.clone(this.legendItemLabelGenerator);
1503 }
1504 if (this.legendItemToolTipGenerator instanceof PublicCloneable) {
1505 clone.legendItemToolTipGenerator = (CategorySeriesLabelGenerator)
1506 ObjectUtilities.clone(this.legendItemToolTipGenerator);
1507 }
1508 if (this.legendItemURLGenerator instanceof PublicCloneable) {
1509 clone.legendItemURLGenerator = (CategorySeriesLabelGenerator)
1510 ObjectUtilities.clone(this.legendItemURLGenerator);
1511 }
1512 return clone;
1513 }
1514
1515 /**
1516 * Returns a domain axis for a plot.
1517 *
1518 * @param plot the plot.
1519 * @param index the axis index.
1520 *
1521 * @return A domain axis.
1522 */
1523 protected CategoryAxis getDomainAxis(CategoryPlot plot, int index) {
1524 CategoryAxis result = plot.getDomainAxis(index);
1525 if (result == null) {
1526 result = plot.getDomainAxis();
1527 }
1528 return result;
1529 }
1530
1531 /**
1532 * Returns a range axis for a plot.
1533 *
1534 * @param plot the plot.
1535 * @param index the axis index.
1536 *
1537 * @return A range axis.
1538 */
1539 protected ValueAxis getRangeAxis(CategoryPlot plot, int index) {
1540 ValueAxis result = plot.getRangeAxis(index);
1541 if (result == null) {
1542 result = plot.getRangeAxis();
1543 }
1544 return result;
1545 }
1546
1547 /**
1548 * Returns a (possibly empty) collection of legend items for the series
1549 * that this renderer is responsible for drawing.
1550 *
1551 * @return The legend item collection (never <code>null</code>).
1552 *
1553 * @see #getLegendItem(int, int)
1554 */
1555 public LegendItemCollection getLegendItems() {
1556 if (this.plot == null) {
1557 return new LegendItemCollection();
1558 }
1559 LegendItemCollection result = new LegendItemCollection();
1560 int index = this.plot.getIndexOf(this);
1561 CategoryDataset dataset = this.plot.getDataset(index);
1562 if (dataset != null) {
1563 int seriesCount = dataset.getRowCount();
1564 for (int i = 0; i < seriesCount; i++) {
1565 if (isSeriesVisibleInLegend(i)) {
1566 LegendItem item = getLegendItem(index, i);
1567 if (item != null) {
1568 result.add(item);
1569 }
1570 }
1571 }
1572
1573 }
1574 return result;
1575 }
1576
1577 /**
1578 * Returns the legend item label generator.
1579 *
1580 * @return The label generator (never <code>null</code>).
1581 *
1582 * @see #setLegendItemLabelGenerator(CategorySeriesLabelGenerator)
1583 */
1584 public CategorySeriesLabelGenerator getLegendItemLabelGenerator() {
1585 return this.legendItemLabelGenerator;
1586 }
1587
1588 /**
1589 * Sets the legend item label generator and sends a
1590 * {@link RendererChangeEvent} to all registered listeners.
1591 *
1592 * @param generator the generator (<code>null</code> not permitted).
1593 *
1594 * @see #getLegendItemLabelGenerator()
1595 */
1596 public void setLegendItemLabelGenerator(
1597 CategorySeriesLabelGenerator generator) {
1598 if (generator == null) {
1599 throw new IllegalArgumentException("Null 'generator' argument.");
1600 }
1601 this.legendItemLabelGenerator = generator;
1602 fireChangeEvent();
1603 }
1604
1605 /**
1606 * Returns the legend item tool tip generator.
1607 *
1608 * @return The tool tip generator (possibly <code>null</code>).
1609 *
1610 * @see #setLegendItemToolTipGenerator(CategorySeriesLabelGenerator)
1611 */
1612 public CategorySeriesLabelGenerator getLegendItemToolTipGenerator() {
1613 return this.legendItemToolTipGenerator;
1614 }
1615
1616 /**
1617 * Sets the legend item tool tip generator and sends a
1618 * {@link RendererChangeEvent} to all registered listeners.
1619 *
1620 * @param generator the generator (<code>null</code> permitted).
1621 *
1622 * @see #setLegendItemToolTipGenerator(CategorySeriesLabelGenerator)
1623 */
1624 public void setLegendItemToolTipGenerator(
1625 CategorySeriesLabelGenerator generator) {
1626 this.legendItemToolTipGenerator = generator;
1627 fireChangeEvent();
1628 }
1629
1630 /**
1631 * Returns the legend item URL generator.
1632 *
1633 * @return The URL generator (possibly <code>null</code>).
1634 *
1635 * @see #setLegendItemURLGenerator(CategorySeriesLabelGenerator)
1636 */
1637 public CategorySeriesLabelGenerator getLegendItemURLGenerator() {
1638 return this.legendItemURLGenerator;
1639 }
1640
1641 /**
1642 * Sets the legend item URL generator and sends a
1643 * {@link RendererChangeEvent} to all registered listeners.
1644 *
1645 * @param generator the generator (<code>null</code> permitted).
1646 *
1647 * @see #getLegendItemURLGenerator()
1648 */
1649 public void setLegendItemURLGenerator(
1650 CategorySeriesLabelGenerator generator) {
1651 this.legendItemURLGenerator = generator;
1652 fireChangeEvent();
1653 }
1654
1655 /**
1656 * Adds an entity with the specified hotspot.
1657 *
1658 * @param entities the entity collection.
1659 * @param dataset the dataset.
1660 * @param row the row index.
1661 * @param column the column index.
1662 * @param hotspot the hotspot (<code>null</code> not permitted).
1663 */
1664 protected void addItemEntity(EntityCollection entities,
1665 CategoryDataset dataset, int row, int column,
1666 Shape hotspot) {
1667 if (hotspot == null) {
1668 throw new IllegalArgumentException("Null 'hotspot' argument.");
1669 }
1670 if (!getItemCreateEntity(row, column)) {
1671 return;
1672 }
1673 String tip = null;
1674 CategoryToolTipGenerator tipster = getToolTipGenerator(row, column);
1675 if (tipster != null) {
1676 tip = tipster.generateToolTip(dataset, row, column);
1677 }
1678 String url = null;
1679 CategoryURLGenerator urlster = getItemURLGenerator(row, column);
1680 if (urlster != null) {
1681 url = urlster.generateURL(dataset, row, column);
1682 }
1683 CategoryItemEntity entity = new CategoryItemEntity(hotspot, tip, url,
1684 dataset, dataset.getRowKey(row), dataset.getColumnKey(column));
1685 entities.add(entity);
1686 }
1687
1688 /**
1689 * Adds an entity to the collection.
1690 *
1691 * @param entities the entity collection being populated.
1692 * @param hotspot the entity area (if <code>null</code> a default will be
1693 * used).
1694 * @param dataset the dataset.
1695 * @param row the series.
1696 * @param column the item.
1697 * @param entityX the entity's center x-coordinate in user space (only
1698 * used if <code>area</code> is <code>null</code>).
1699 * @param entityY the entity's center y-coordinate in user space (only
1700 * used if <code>area</code> is <code>null</code>).
1701 *
1702 * @since 1.0.13
1703 */
1704 protected void addEntity(EntityCollection entities, Shape hotspot,
1705 CategoryDataset dataset, int row, int column,
1706 double entityX, double entityY) {
1707 if (!getItemCreateEntity(row, column)) {
1708 return;
1709 }
1710 Shape s = hotspot;
1711 if (hotspot == null) {
1712 double r = getDefaultEntityRadius();
1713 double w = r * 2;
1714 if (getPlot().getOrientation() == PlotOrientation.VERTICAL) {
1715 s = new Ellipse2D.Double(entityX - r, entityY - r, w, w);
1716 }
1717 else {
1718 s = new Ellipse2D.Double(entityY - r, entityX - r, w, w);
1719 }
1720 }
1721 String tip = null;
1722 CategoryToolTipGenerator generator = getToolTipGenerator(row, column);
1723 if (generator != null) {
1724 tip = generator.generateToolTip(dataset, row, column);
1725 }
1726 String url = null;
1727 CategoryURLGenerator urlster = getItemURLGenerator(row, column);
1728 if (urlster != null) {
1729 url = urlster.generateURL(dataset, row, column);
1730 }
1731 CategoryItemEntity entity = new CategoryItemEntity(s, tip, url,
1732 dataset, dataset.getRowKey(row), dataset.getColumnKey(column));
1733 entities.add(entity);
1734 }
1735
1736 // === DEPRECATED CODE ===
1737
1738 /**
1739 * The item label generator for ALL series.
1740 *
1741 * @deprecated This field is redundant and deprecated as of version 1.0.6.
1742 */
1743 private CategoryItemLabelGenerator itemLabelGenerator;
1744
1745 /**
1746 * The tool tip generator for ALL series.
1747 *
1748 * @deprecated This field is redundant and deprecated as of version 1.0.6.
1749 */
1750 private CategoryToolTipGenerator toolTipGenerator;
1751
1752 /**
1753 * The URL generator.
1754 *
1755 * @deprecated This field is redundant and deprecated as of version 1.0.6.
1756 */
1757 private CategoryURLGenerator itemURLGenerator;
1758
1759 /**
1760 * Sets the item label generator for ALL series and sends a
1761 * {@link RendererChangeEvent} to all registered listeners.
1762 *
1763 * @param generator the generator (<code>null</code> permitted).
1764 *
1765 * @deprecated This method should no longer be used (as of version 1.0.6).
1766 * It is sufficient to rely on {@link #setSeriesItemLabelGenerator(int,
1767 * CategoryItemLabelGenerator)} and
1768 * {@link #setBaseItemLabelGenerator(CategoryItemLabelGenerator)}.
1769 */
1770 public void setItemLabelGenerator(CategoryItemLabelGenerator generator) {
1771 this.itemLabelGenerator = generator;
1772 fireChangeEvent();
1773 }
1774
1775 /**
1776 * Returns the tool tip generator that will be used for ALL items in the
1777 * dataset (the "layer 0" generator).
1778 *
1779 * @return A tool tip generator (possibly <code>null</code>).
1780 *
1781 * @see #setToolTipGenerator(CategoryToolTipGenerator)
1782 *
1783 * @deprecated This method should no longer be used (as of version 1.0.6).
1784 * It is sufficient to rely on {@link #getSeriesToolTipGenerator(int)}
1785 * and {@link #getBaseToolTipGenerator()}.
1786 */
1787 public CategoryToolTipGenerator getToolTipGenerator() {
1788 return this.toolTipGenerator;
1789 }
1790
1791 /**
1792 * Sets the tool tip generator for ALL series and sends a
1793 * {@link org.jfree.chart.event.RendererChangeEvent} to all registered
1794 * listeners.
1795 *
1796 * @param generator the generator (<code>null</code> permitted).
1797 *
1798 * @see #getToolTipGenerator()
1799 *
1800 * @deprecated This method should no longer be used (as of version 1.0.6).
1801 * It is sufficient to rely on {@link #setSeriesToolTipGenerator(int,
1802 * CategoryToolTipGenerator)} and
1803 * {@link #setBaseToolTipGenerator(CategoryToolTipGenerator)}.
1804 */
1805 public void setToolTipGenerator(CategoryToolTipGenerator generator) {
1806 this.toolTipGenerator = generator;
1807 fireChangeEvent();
1808 }
1809
1810 /**
1811 * Sets the item URL generator for ALL series and sends a
1812 * {@link RendererChangeEvent} to all registered listeners.
1813 *
1814 * @param generator the generator.
1815 *
1816 * @deprecated This method should no longer be used (as of version 1.0.6).
1817 * It is sufficient to rely on {@link #setSeriesItemURLGenerator(int,
1818 * CategoryURLGenerator)} and
1819 * {@link #setBaseItemURLGenerator(CategoryURLGenerator)}.
1820 */
1821 public void setItemURLGenerator(CategoryURLGenerator generator) {
1822 this.itemURLGenerator = generator;
1823 fireChangeEvent();
1824 }
1825
1826
1827 }